﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Web.UI.HtmlControls;

using AjaxControlToolkit;
using BizElements.Core;
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>Base class that represents a binding between an entity and its parent entities.</summary>
    /// <typeparam name="TParentTable">Metadata class for table that contains parent records.</typeparam>
    public abstract class ParentBindingBase<TParentTable> : IParentBinding
        where TParentTable : IDbTable, new()
    {
        IDataObjectDescriptor parentEntityDescriptor;

        #region CTor.

        /// <summary>Initializes a new instance of ParentLookupBinding class.</summary>
        /// <param name="parentEntityDescriptor">Parent entity descriptor. Additional features are available for composite entities.</param>
        /// <param name="foreignKeyField">FK field.</param>
        /// <param name="boundLookupControl">Lookup control bound to FK field.</param>
        /// <param name="searchProviderBuilder">Search provider builder.</param>
        /// <param name="parentFormUrl">Parent form URL. Required if create new parent and open parent details are used.</param>
        /// <param name="openParentDetailsControl">Optional open parent details control.</param>
        /// <param name="createNewParentControl">Optional create new parent control.</param>
        /// <param name="canClearSelection">Indicates whether bound control should render UI that allows users to clear selection or select a <b>null</b> value.</param>
        protected ParentBindingBase(IDataObjectDescriptor parentEntityDescriptor, IDbColumn foreignKeyField, IBoundLookupControl boundLookupControl, SearchProviderBuilder searchProviderBuilder, string parentFormUrl, Control openParentDetailsControl, Control createNewParentControl, bool canClearSelection)
        {
            this.parentEntityDescriptor = parentEntityDescriptor;
            this.ForeignKeyPropertyName = foreignKeyField.PropertyName;
            this.BoundControlId = boundLookupControl.ID;
            if (searchProviderBuilder != null)
                this.SeachProviderBuilderControlId = searchProviderBuilder.ID;
            this.ParentFormUrl = parentFormUrl;
            if (openParentDetailsControl != null)
                this.OpenParentDetailsControlId = openParentDetailsControl.ID;
            if (createNewParentControl != null)
                this.CreateNewParentControlId = createNewParentControl.ID;
            
            this.CanClearSelection = canClearSelection;
        }

        /// <summary>Initializes a new instance of ParentListBinding class.</summary>
        /// <param name="parentEntityDescriptor">Parent entity descriptor. Additional features are available for composite entities.</param>
        /// <param name="foreignKeyField">FK field.</param>
        /// <param name="boundListControl">List control bound to FK field.</param>
        /// <param name="parentFormUrl">Parent form URL. Required if create new parent and open parent details are used.</param>
        /// <param name="openParentDetailsControl">Optional open parent details control.</param>
        /// <param name="createNewParentControl">Optional create new parent control.</param>
        /// <param name="canClearSelection">Indicates whether bound control should render UI that allows users to clear selection or select a <b>null</b> value.</param>
        protected ParentBindingBase(IDataObjectDescriptor parentEntityDescriptor, IDbColumn foreignKeyField, IBoundListControl boundListControl, string parentFormUrl, Control openParentDetailsControl, Control createNewParentControl, bool canClearSelection)
        {
            this.parentEntityDescriptor = parentEntityDescriptor;
            this.ForeignKeyPropertyName = foreignKeyField.PropertyName;
            this.ForeignKeyPropertyDataType = foreignKeyField.DataType;
            this.BoundControlId = boundListControl.ID;
            this.ParentFormUrl = parentFormUrl;
            if (openParentDetailsControl != null)
                this.OpenParentDetailsControlId = openParentDetailsControl.ID;
            if (createNewParentControl != null)
                this.CreateNewParentControlId = createNewParentControl.ID;

            this.CanClearSelection = canClearSelection;
        }

        #endregion

        #region Properties.

        /// <summary>Foreign key property name. Only simple single-field FKs are supported.</summary>
        public string ForeignKeyPropertyName { get; private set; }

        /// <summary>Foreign key property data type. Only simple single-field FKs are supported.</summary>
        public Type ForeignKeyPropertyDataType { get; private set; }

        /// <summary>Bound control ID: lookup or list control.</summary>
        public string BoundControlId { get; private set; }

        /// <summary>Parent form URL.</summary>
        public string ParentFormUrl { get; private set; }

        /// <summary>Search provider builder control ID if lookup control is bound to FK.</summary>
        public string SeachProviderBuilderControlId { get; private set; }

        /// <summary>Optional create new parent control ID.</summary>
        public string OpenParentDetailsControlId { get; private set; }

        /// <summary>Optional create open parent details control ID.</summary>
        public string CreateNewParentControlId { get; private set; }

        /// <summary>Indicates whether bound control should render UI that allows users to clear selection or select a <b>null</b> value.</summary>
        public bool CanClearSelection { get; private set; }

        #endregion

        #region Bound control: ILookupControl or ListControl.

        ILookupControl lookupControl;
        IBoundListControl listControl;

        /// <summary>Gets lookup control to whick FK field is bound.</summary>
        /// <param name="parentBizobjControl">A user control that contains bound lookup or list control.</param>
        /// <returns>Lookup control or null if list control is used.</returns>
        public ILookupControl GetLookupControl(Control parentBizobjControl)
        {
            EnsureBoundControl(parentBizobjControl);
            return this.lookupControl;
        }

        /// <summary>Gets list control to whick FK field is bound.</summary>
        /// <param name="parentBizobjControl">A user control that contains bound lookup or list control.</param>
        /// <returns>List control or null if lookup control is used.</returns>
        public IBoundListControl GetListControl(Control parentBizobjControl)
        {
            EnsureBoundControl(parentBizobjControl);
            return this.listControl;
        }

        private void EnsureBoundControl(Control parentBizobjControl)
        {
            if (this.lookupControl != null || this.listControl != null)
                return;

            Control ctl = WebUtil.RecursivelyFindControl(parentBizobjControl, this.BoundControlId);
            if (ctl is ILookupControl)
                this.lookupControl = (ILookupControl)ctl;
            else if (ctl is ListControl)
                this.listControl = (IBoundListControl)ctl;
            else
                throw new InvalidOperationException("No lookup or list control binding data.");
        }

        #endregion

        #region GetSearchProviderBuilderComponent.

        SearchProviderBuilder searchProviderBuilderComponent;

        /// <summary>Gets search provider builder component.</summary>
        /// <param name="parentBizobjControl">A user control that contains bound lookup or list control.</param>
        /// <returns>Provider builder control or null if list control is used.</returns>
        public SearchProviderBuilder GetSearchProviderBuilderComponent(Control parentBizobjControl)
        {
            if (this.searchProviderBuilderComponent == null && this.SeachProviderBuilderControlId != null)
                this.searchProviderBuilderComponent = WebUtil.RecursivelyFindControl(parentBizobjControl, this.SeachProviderBuilderControlId) as SearchProviderBuilder;

            return this.searchProviderBuilderComponent;
        }

        #endregion

        #region GetSelectedValue and GetText.

        /// <summary>Gets FK value selected in bound control.</summary>
        /// <param name="parentBizobjControl">A user control that contains bound lookup or list control.</param>
        /// <returns>FK value or null if nothing is selected.</returns>
        public string GetSelectedValue(Control parentBizobjControl)
        {
            EnsureBoundControl(parentBizobjControl);
            if (this.lookupControl != null)
                return TextUtil.TrimNullableString(lookupControl.SelectedValue);
            else if (this.listControl != null)
                return TextUtil.TrimNullableString(this.listControl.SelectedValue);
            else
                throw new InvalidOperationException("No lookup or list control binding data.");
        }

        /// <summary>Gets text selelected in bound control.</summary>
        /// <param name="parentBizobjControl">A user control that contains bound lookup or list control.</param>
        /// <returns>Default parent property value or null if nothing is selected.</returns>
        public string GetText(Control parentBizobjControl)
        {
            EnsureBoundControl(parentBizobjControl);
            if (this.lookupControl != null)
                return lookupControl.Text;
            else if (this.listControl != null)
                return this.listControl.Text;
            else
                throw new InvalidOperationException("No lookup or list control binding data.");
        }

        #endregion

        #region New... and Details... controls.

        Control createNewParentControl;

        /// <summary>Gets control that creates a new parent entity via another form.</summary>
        /// <param name="parentBizobjControl">Parent user control.</param>
        /// <returns>Control or null if not configured.</returns>
        public Control GetCreateNewParentControl(Control parentBizobjControl)
        {
            if (this.createNewParentControl == null && this.CreateNewParentControlId != null)
                this.createNewParentControl = WebUtil.RecursivelyFindControl(parentBizobjControl, this.CreateNewParentControlId);

            return this.createNewParentControl;
        }

        Control openParentDetailsControl;

        /// <summary>Gets control that opens parent details in anoother form.</summary>
        /// <param name="parentBizobjControl">Parent user control.</param>
        /// <returns>Control or null if not configured.</returns>
        public Control GetOpenParentDetailsControl(Control parentBizobjControl)
        {
            if (this.openParentDetailsControl == null && this.OpenParentDetailsControlId != null)
                this.openParentDetailsControl = WebUtil.RecursivelyFindControl(parentBizobjControl, this.OpenParentDetailsControlId);

            return this.openParentDetailsControl;
        }

        #endregion

        #region Get*, New*

        /// <summary>Returns metadata of table that contains parent records.</summary>
        /// <returns>Table metada.</returns>
        public IDbTable GetParentTable()
        {
            return new TParentTable();
        }

        /// <summary>Return basic entity descriptor.</summary>
        /// <returns>An object that implements IDataObjectDescriptor interface.</returns>
        public IDataObjectDescriptor GetDataObjectDescriptor()
        {
            return this.parentEntityDescriptor;
        }

        /// <summary>Tries to get descriptor of default business object imlementation for the parent table.</summary>
        /// <returns>Descriptor od default business layer class for parent table, typically a concrete class without "Entity" suffix; <b>null</b> if no default concrete implementation exists.</returns>
        public IBusinessObjectDescriptor TryGetBusinessObjectDescriptor()
        {
            return this.parentEntityDescriptor as IBusinessObjectDescriptor;
        }

        /// <summary>Returns null. Must implement in derived classes.</summary>
        /// <returns>Null.</returns>
        public virtual ISearchEngineBuilder NewSearchEngineBuilder()
        {
            return null;
        }

        /// <summary>Returns null. Must implement in derived classes.</summary>
        /// <param name="actor">Actor requesting data.</param>
        /// <returns>Null.</returns>
        public virtual ICodeList GetCodeList(IActor actor)
        {
            return null;
        }

        #endregion
    }
}
